home *** CD-ROM | disk | FTP | other *** search
/ Just Call Me Internet / Just Call Me Internet.iso / prog / atari / c / uemlsrc / main.c < prev    next >
Encoding:
C/C++ Source or Header  |  1987-08-24  |  22.5 KB  |  645 lines

  1. /*
  2.  * This program is in public domain; written by Dave G. Conroy.
  3.  * This file contains the main driving routine for the Micro-
  4.  * EMACS screen editor.  This version began as the original
  5.  * small version by DGC.  It has been extensively expanded
  6.  * using versions 35 (a hybrid) and 36 (a direct descendent).
  7.  * This version includes a number of text functions that are
  8.  * not found in others and a word wrap algorithm different from
  9.  * 35 or 36.  It also contains a kermit module, real page movement,
  10.  * printer support and a shell command.  It is sort of a cross
  11.  * between ME and Perfect Writer version 1.03.
  12.  */
  13.  
  14. #include <stdio.h>
  15. #include <ctype.h>
  16. #include <osbind.h>
  17. #include "ed.h"
  18. #if ST
  19. #include "keycode.h"
  20. #endif
  21.  
  22. #if     VMS
  23. #include        <ssdef.h>
  24. #define GOOD    (SS$_NORMAL)
  25. #endif
  26.  
  27. #ifndef GOOD
  28. #define GOOD    0
  29. #endif
  30.  
  31. char *version = "v. 33 25-January-1987";
  32.  
  33. int     currow;                         /* Working cursor row           */
  34. int     curcol;                         /* Working cursor column        */
  35. int     fillcol = 76;                   /* Current fill column          */
  36. int     indcol;                         /* Current indent column        */
  37. int     thisflag;                       /* Flags, this command          */
  38. int     lastflag;                       /* Flags, last command          */
  39. int     curgoal;                        /* Goal column                  */
  40. int     isnprint;                       /* Print buffer in use          */
  41. int     glmode = BMNWRAP;               /* Begin in fundamental mode    */
  42. BUFFER  *curbp;                         /* Current buffer               */
  43. WINDOW  *curwp;                         /* Current window               */
  44. BUFFER  *bheadp;                        /* BUFFER listhead              */
  45. WINDOW  *wheadp;                        /* WINDOW listhead              */
  46. BUFFER  *blistp = NULL;                 /* Buffer list BUFFER           */
  47. BUFFER  *bmacrp = NULL;                 /* Compiled macros BUFFER       */
  48. short   kbdm[NKBDM] = CTLX|')';         /* Macro                        */
  49. short   *kbdmip;                        /* Input  for above             */
  50. short   *kbdmop;                        /* Output for above             */
  51. char    pat[NPAT];                      /* Pattern                      */
  52. char    rpat[NPAT];                     /* Replacement pattern          */
  53. char    prnhdr[NPAT];                   /* Print header                 */
  54. char    prndate[NPAT];                  /* Print date                   */
  55. char    lastbuf[NBUFN];                 /* Last buffer name             */
  56. char    defpath[128];                   /* default path name            */
  57. int     defdrive;                       /* default drive                */
  58. long    buserr;                         /* Address of gemdos busserr    */
  59. long    adderr;                         /* Address of gemdos address err*/
  60. long    progend;                        /* end of program after init    */
  61.  
  62. main(argc, argv)
  63. int argc;
  64. char *argv[];
  65. {
  66.         register int    c;
  67.         register int    f;
  68.         register int    n;
  69.         register int    mflag;
  70.         register int    prncnt;
  71.         register int    basec;
  72.         extern   int    errexit();
  73.         extern   long   sbrk();
  74.  
  75. #if     ALCYON
  76.         fclose(stdin);                          /* reduce overhead for */
  77.         fclose(stdout);                         /* unused file pointers */
  78.         fclose(stderr);
  79. #endif
  80.         isnprint = FALSE;
  81.         prncnt = 0;
  82.  
  83.         if (access("cc.ini",4) == NULL)         /* cc drive assignments */
  84.                 commfil(FALSE,HUGE);            /* and function key bind*/
  85.         strcpy(lastbuf, "main");                /* Work out the name of */
  86.         if (argc > 1)                           /* the default buffer.  */
  87.                 makename(lastbuf, argv[1]);
  88.         edinit(lastbuf);                        /* Buffers, windows.    */
  89.         vtinit();                               /* Displays.            */
  90.         update();
  91. #if     ST
  92.         defdrive = Dgetdrv();
  93.         Dgetpath(defpath,0);
  94.         buserr = Setexc(2,-1L);
  95.         adderr = Setexc(3,-1L);
  96.         Setexc(2,&errexit);                     /* Clean up screen on err */
  97.         Setexc(3,&errexit);
  98. #endif
  99.         if (access("uemail.mcr",4) == NULL)     /* default macro file   */
  100.                 {
  101.                 update();
  102.                 loadmac(FALSE);
  103.                 }
  104.         progend = sbrk(0);
  105.         if (argc > 1) {
  106.                 update();                       /* You have to update   */
  107.                 readin(argv[1]);                /* in case "[New file]" */
  108.         }
  109.         lastflag = 0;                           /* Fake last flags.     */
  110. loop:
  111.         update();                               /* Fix up the screen    */
  112.  
  113.         /* This is the print buffer code.  It's very rudimentary, but it
  114.          * works as long as the buffer in the printer does not get filled.
  115.          * On the initial call to the function print(), the printer is
  116.          * sent one 1536 byte buffer of data and isnprint is set to
  117.          * TRUE.  From then on the printer is sent 384 bytes of data after
  118.          * an arbitrary number of keystrokes is entered if the printer
  119.          * is ready.
  120.          */
  121.  
  122.         if(isnprint)                            /* We are printing a file */
  123.                 if (prncnt++ > 16)              /* Keystroke count      */
  124.                         if(PRNRDY)              /* Check LST: status */
  125.                                 {
  126.                                 prnbuf();       /* Send a buffer full */
  127.                                 prncnt = 0;
  128.                                 }
  129.         f = getkey();
  130.         if (shiftstatus == 17 || shiftstatus == 18)     /* CapsLock + Shift */
  131.                 c = tolower(f);
  132.         else
  133.                 c = f;
  134.         if (mpresf != FALSE) {
  135.                 mlerase();
  136.                 update();
  137.         }
  138.         f = FALSE;
  139.         n = 1;
  140.  
  141.         /* do META-# processing if needed */
  142.  
  143.         basec = c & ~META;              /* strip meta char off if there */
  144.         if ((c & META) && ((basec >= '0' && basec <= '9') || basec == '-')) {
  145.                  f = TRUE;               /* there is a # arg */
  146.                  n = 0;             /* start with a zero default */
  147.                  mflag = 1;             /* current minus flag */
  148.                  c = basec;             /* strip the META */
  149.                  while ((c >= '0' && c <= '9') || (c == '-')) {
  150.                           if (c == '-') {
  151.                                    /* already hit a minus or digit? */
  152.                                    if ((mflag == -1) || (n != 0))
  153.                                             break;
  154.                                    mflag = -1;
  155.                           } else {
  156.                                    n = n * 10 + (c - '0');
  157.                           }
  158.                           if ((n == 0) && (mflag == -1))  /* lonely - */
  159.                                    mlwrite("Arg:");
  160.                           else
  161.                                    mlwrite("Arg: %d",n * mflag);
  162.  
  163.                           c = getkey();   /* get the next key */
  164.                  }
  165.                  n = n * mflag;  /* figure in the sign */
  166.         }
  167.  
  168.         /* ^U expansion */
  169.  
  170.         if (c == (CTRL|'U')) {                  /* ^U, start argument   */
  171.                 f = TRUE;
  172.                 n = 4;                          /* with argument of 4 */
  173.                 mflag = 0;                      /* that can be discarded. */
  174.                 mlwrite("Arg: 4");
  175.                 while ((c=getkey()) >='0' && c<='9' || c==(CTRL|'U') || c=='-'){
  176.                         if (c == (CTRL|'U'))
  177.                                 n = n*4;
  178.                         /*
  179.                          * If dash, and start of argument string, set arg.
  180.                          * to -1.  Otherwise, insert it.
  181.                          */
  182.                         else if (c == '-') {
  183.                                 if (mflag)
  184.                                         break;
  185.                                 n = 0;
  186.                                 mflag = -1;
  187.                         }
  188.                         /*
  189.                          * If first digit entered, replace previous argument
  190.                          * with digit and set sign.  Otherwise, append to arg.
  191.                          */
  192.                         else {
  193.                                 if (!mflag) {
  194.                                         n = 0;
  195.                                         mflag = 1;
  196.                                 }
  197.                                 n = 10*n + c - '0';
  198.                         }
  199.                         mlwrite("Arg: %d", (mflag >=0) ? n : (n ? -n : -1));
  200.                 }
  201.                 /*
  202.                  * Make arguments preceded by a minus sign negative and change
  203.                  * the special argument "^U -" to an effective "^U -1".
  204.                  */
  205.                 if (mflag == -1) {
  206.                         if (n == 0)
  207.                                 n++;
  208.                         n = -n;
  209.                 }
  210.         }
  211.         if (c == (CTRL|'X')) {                  /* ^X is a prefix       */
  212.                 mlwrite("C-X: ");
  213.                 c = CTLX | getctl();
  214.         }
  215.         if (kbdmip != NULL) {                   /* Save macro strokes.  */
  216.                 if (c!=(CTLX|')') && kbdmip>&kbdm[NKBDM-6]) {
  217.                         ctrlg(FALSE, 0);
  218.                         goto loop;
  219.                 }
  220.                 if (f != FALSE) {
  221.                         *kbdmip++ = (CTRL|'U');
  222.                         *kbdmip++ = n;
  223.                 }
  224.                 *kbdmip++ = c;
  225.         }
  226.         execute(c, f, n);                       /* Do it.               */
  227.         goto loop;
  228. }
  229.  
  230. /*
  231.  * Initialize all of the buffers
  232.  * and windows. The buffer name is passed down as
  233.  * an argument, because the main routine may have been
  234.  * told to read in a file by default, and we want the
  235.  * buffer name to be right.
  236.  */
  237. edinit(bname)
  238. char    bname[];
  239. {
  240.         register BUFFER *bp;
  241.         register WINDOW *wp;
  242.  
  243.         bp = bfind(bname, TRUE, 0);             /* First buffer         */
  244.         wp = (WINDOW *) malloc(sizeof(WINDOW)); /* First window         */
  245.         if (bp==NULL || wp==NULL)
  246.                 exit(1);
  247.         curbp  = bp;                            /* Make this current    */
  248.         wheadp = wp;
  249.         curwp  = wp;
  250.         wp->w_wndp  = NULL;                     /* Initialize window    */
  251.         wp->w_bufp  = bp;
  252.         bp->b_nwnd  = 1;                        /* Displayed.           */
  253.         wp->w_linep = bp->b_linep;
  254.         wp->w_dotp  = bp->b_linep;
  255.         wp->w_doto  = 0;
  256.         wp->w_markp = wp->w_dotp;
  257.         wp->w_marko = 0;
  258.         wp->w_toprow = 0;
  259.         wp->w_ntrows = term.t_nrow-1;           /* "-1" for mode line.  */
  260.         wp->w_force = 0;
  261.         wp->w_flag  = WFMODE|WFHARD;            /* Full.                */
  262. }
  263.  
  264. /*
  265.  * Read in a key.
  266.  * Do the standard keyboard preprocessing.
  267.  * Convert the keys to the internal character set. On
  268.  * the LK201, which lacks a reasonable ESC key, make the
  269.  * grave accent a meta key too; this is a fairly common
  270.  * customization around Digital. Also read and decode
  271.  * the arrow keys, and other special keys. This is
  272.  * done in Rainbow mode; does this work on all
  273.  * the terminals with LK201 keyboards?
  274.  */
  275. getkey()
  276. {
  277.         register int    c;
  278. #if     LK201
  279.         register int    n;
  280. loop:
  281.         c = (*term.t_getchar)();
  282.         if (c == AGRAVE) {                      /* Alternate M- prefix. */
  283.                 c = getctl();
  284.                 return (META | c);
  285.         }
  286.         if (c == METACH) {                      /* M-, or special key.  */
  287.                 c = (*term.t_getchar)();
  288.                 if (c == '[') {                 /* Arrows and extras.   */
  289.                         c = (*term.t_getchar)();
  290.                         if (c == 'A')
  291.                                 return (CTRL | 'P');
  292.                         if (c == 'B')
  293.                                 return (CTRL | 'N');
  294.                         if (c == 'C')
  295.                                 return (CTRL | 'F');
  296.                         if (c == 'D')
  297.                                 return (CTRL | 'B');
  298.                         if (c>='0' && c<='9') {
  299.                                 n = 0;
  300.                                 do {
  301.                                         n = 10*n + c - '0';
  302.                                         c = (*term.t_getchar)();
  303.                                 } while (c>='0' && c<='9');
  304.                                 if (c=='~' && n<=34 && (c=lkmap[n])!=0)
  305.                                         return (c);
  306.                         }
  307.                         goto loop;
  308.                 }
  309.                 if (c == 'O') {
  310.                         c = (*term.t_getchar)();
  311.                         if (c == 'P')           /* PF1 => M-X (Future)  */
  312.                                 return (META | 'X');
  313.                         if (c == 'Q')           /* PF2 => C-Q           */
  314.                                 return (CTRL | 'Q');
  315.                         if (c == 'R')           /* PF3 => C-S           */
  316.                                 return (CTRL | 'S');
  317.                         if (c == 'S')           /* PF4 => C-R           */
  318.                                 return (CTRL | 'R');
  319.                         goto loop;
  320.                 }
  321.                 if (c>='a' && c<='z')           /* Force to upper       */
  322.                         c -= 0x20;
  323.                 if (c>=0x00 && c<=0x1F)         /* C0 control -> C-     */
  324.                         c = CTRL | (c+'@');
  325.                 return (META | c);
  326.         }
  327. #endif
  328. #if     VT100
  329. loop:
  330.         c = (*term.t_getchar)();
  331.         if (c == METACH) {                      /* Apply M- prefix      */
  332.                 c = (*term.t_getchar)();
  333.                 if (c == '[') {                 /* Arrow keys.          */
  334.                         c = (*term.t_getchar)();
  335.                         if (c == 'A')
  336.                                 return (CTRL | 'P');
  337.                         if (c == 'B')
  338.                                 return (CTRL | 'N');
  339.                         if (c == 'C')
  340.                                 return (CTRL | 'F');
  341.                         if (c == 'D')
  342.                                 return (CTRL | 'B');
  343.                         goto loop;
  344.                 }
  345.                 if (c == 'O') {
  346.                         c = (*term.t_getchar)();
  347.                         if (c == 'P')           /* PF1 => M-X (Future)  */
  348.                                 return (META | 'X');
  349.                         if (c == 'Q')           /* PF2 => C-Q           */
  350.                                 return (CTRL | 'Q');
  351.                         if (c == 'R')           /* PF3 => C-S           */
  352.                                 return (CTRL | 'S');
  353.                         if (c == 'S')           /* PF4 => C-R           */
  354.                                 return (CTRL | 'R');
  355.                         goto loop;
  356.                 }
  357.                 if (c>='a' && c<='z')           /* Force to upper       */
  358.                         c -= 0x20;
  359.                 if (c>=0x00 && c<=0x1F)         /* C0 control -> C-     */
  360.                         c = CTRL | (c+'@');
  361.                 return (META | c);
  362.         }
  363. #endif
  364.         c = (*term.t_getchar)();
  365. #if     ST
  366.         if (scancode == 0x70 && shiftstatus != 16)
  367.                 return (CTRL|'X');
  368. #endif
  369.         if (c == METACH) {                      /* Apply M- prefix      */
  370.                 mlwrite("Meta: ");
  371.                 c = getctl();
  372.                 return (META | c);
  373.         }
  374.         if (c == CTRLCH) {                      /* Apply C- prefix      */
  375.                 c = getctl();
  376.                 return (CTRL | c);
  377.         }
  378.         if (c == CTMECH) {                      /* Apply C-M- prefix    */
  379.                 c = getctl();
  380.                 return (CTRL | META | c);
  381.         }
  382. #if     ST
  383.         /* use special keys or number pad (code >= 0x63) ?
  384.          * 0x03 = scancode for ^@ (setmark).
  385.          */
  386.         if ((c == NULL && scancode != 0x03) || scancode >= 0x4a)
  387.                 if (scancode > 0x32)
  388.                         {
  389.                         /* if CapsLock and number pad, use real numbers */
  390.                         if (shiftstatus == 16 && (scancode > 0x62 ||
  391.                                 scancode == 0x4a || scancode == 0x4e))
  392.                                 return(c);
  393.                         else
  394.                                 return (SPEC | scancode);
  395.                         }
  396.                 else    {
  397.                         c = keyscan[scancode];
  398.                         return (META | c);
  399.                         }
  400. #endif
  401.         if (c>=0x00 && c<=0x1F)                 /* C0 control -> C-     */
  402.                 c = CTRL | (c+'@');
  403.         return (c);
  404. }
  405.  
  406. /*
  407.  * Get a key.
  408.  * Apply control modifications
  409.  * to the read key.
  410.  */
  411. getctl()
  412. {
  413.         register int    c;
  414.  
  415.         c = (*term.t_getchar)();
  416. #if     ST
  417.         if (scancode == 0x70 && shiftstatus != 16)
  418.                 return (SPEC|'p');
  419. #endif
  420.         if (c>='a' && c<='z')                   /* Force to upper       */
  421.                 c -= 0x20;
  422.         if (c>=0x00 && c<=0x1F)                 /* C0 control -> C-     */
  423.                 c = CTRL | (c+'@');
  424.         return (c);
  425. }
  426.  
  427. #if     ST
  428. /* ERREXIT  Since this toy insists on Bus errors for so many memory
  429.  * accesses, we need a way to exit gracefully.
  430.  */
  431. errexit()
  432. {
  433.         if(mlyesno("FATAL: Buss error. Attempt to save files")!=TRUE)
  434.                 quit(TRUE,FALSE);
  435.         quit(FALSE,FALSE);
  436.         exit(-1);
  437. }
  438. #endif
  439.  
  440. /*
  441. /*
  442.  * Fancy quit command, as implemented
  443.  * by Norm. If any buffer has changed
  444.  * write that buffer out. Otherwise simply exit.
  445.  */
  446. quickexit(f, n)
  447. int f, n;
  448. {
  449.         register BUFFER *bp;    /* for buffer scan */
  450.  
  451.         bp = bheadp;
  452.         while (bp != NULL) {
  453.                 if ((bp->b_flag&BFCHG) != 0     /* Changed.             */
  454.                 && (bp->b_flag&BFTEMP) == 0){   /* Real.        */
  455.                         curbp = bp;
  456.                         mlwrite("[Saving %s]",bp->b_fname);
  457.                                 filesave(f, n);
  458.                 }
  459.         bp = bp->b_bufp;                /* next buffer */
  460.         }
  461.         quit(f, n);                             /* conditionally quit   */
  462. }
  463.  
  464. /*
  465.  * Quit command. If an argument, always
  466.  * quit. Otherwise confirm if a buffer has been
  467.  * changed and not written out. Normally bound
  468.  * to "C-X C-C".
  469.  */
  470. quit(f, n)
  471. int f, n;
  472. {
  473.         register BUFFER *bp;    /* for buffer scan */
  474.         register int    s;
  475.  
  476.         bp = bheadp;
  477.         if (f != FALSE                          /* Argument forces it.  */
  478.         || (anycb() == FALSE                    /* All buffers clean.   */
  479.         && isnprint == FALSE)) {                /* Are we printing?     */
  480.                 vttidy();
  481. #if     ST
  482.                 Dsetdrv(defdrive);
  483.                 Dsetpath(defpath);
  484.                 Setexc(2,buserr);
  485.                 Setexc(3,adderr);
  486. #endif
  487.                 exit((n!=FALSE) ? GOOD: -1);
  488.         }
  489.         if (isnprint)
  490.                 if ((s=mlyesno("Abandon printing")) != TRUE)
  491.                         return(s);
  492.         while (bp != NULL) {
  493.                 if ((bp->b_flag&BFCHG) != 0     /* Changed.     */
  494.                 && (bp->b_flag&BFTEMP) == 0){   /* Real.        */
  495.                         curbp = bp;
  496.                         mlwrite("Save %s [y/n]?",bp->b_fname);
  497.                         switch(ttgetc()) {
  498.                                 case 'Y':
  499.                                 case 'y':
  500.                                         filesave(f, n);
  501.                                         break;
  502.                                 case 0x07:
  503.                                         return(ctrlg(f,n));
  504.                                 default:
  505.                                         break;
  506.                         }
  507.                 }
  508.         bp = bp->b_bufp;                /* next buffer */
  509.         }
  510.         if (anycb())    /* check once more */
  511.                 if ((s=mlyesno("Abandon modified buffers")) != TRUE)
  512.                         return(s);
  513.         vttidy();
  514. #if     ST
  515.         Setexc(2,buserr);
  516.         Setexc(3,adderr);
  517. #endif
  518.         exit((n!=FALSE) ? GOOD : -1);
  519. }
  520.  
  521. /*
  522.  * Begin a keyboard macro.
  523.  * Error if not at the top level
  524.  * in keyboard processing. Set up
  525.  * variables and return.
  526.  */
  527. ctlxlp(f, n)
  528. int f, n;
  529. {
  530.         if (kbdmip!=NULL || kbdmop!=NULL) {
  531.                 mlwrite("Not now");
  532.                 return (FALSE);
  533.         }
  534.         mlwrite("[Start macro]");
  535.         kbdmip = &kbdm[0];
  536.         return (TRUE);
  537. }
  538.  
  539. /*
  540.  * End keyboard macro. Check for
  541.  * the same limit conditions as the
  542.  * above routine. Set up the variables
  543.  * and return to the caller.
  544.  */
  545. ctlxrp(f, n)
  546. int f, n;
  547. {
  548.         if (kbdmip == NULL) {
  549.                 mlwrite("Not now");
  550.                 return (FALSE);
  551.         }
  552.         mlwrite("[End macro]");
  553.         kbdmip = NULL;
  554.         return (TRUE);
  555. }
  556.  
  557. /*
  558.  * Execute a macro.
  559.  * The command argument is the
  560.  * number of times to loop. Quit as
  561.  * soon as a command gets an error.
  562.  * Return TRUE if all ok, else
  563.  * FALSE.
  564.  */
  565. ctlxe(f, n)
  566. register int f, n;
  567. {
  568.         register int    c;
  569.         register int    af;
  570.         register int    an;
  571.         register int    s;
  572.  
  573.         if (kbdmip!=NULL || kbdmop!=NULL) {
  574.                 mlwrite("Not now");
  575.                 return (FALSE);
  576.         }
  577.         if (n <= 0)
  578.                 return (TRUE);
  579.         do {
  580.                 kbdmop = &kbdm[0];
  581.                 do {
  582.                         af = FALSE;
  583.                         an = 1;
  584.                         if ((c = *kbdmop++) == (CTRL|'U')) {
  585.                                 af = TRUE;
  586.                                 an = *kbdmop++;
  587.                                 c  = *kbdmop++;
  588.                         }
  589.                         s = TRUE;
  590.                 } while (c!=(CTLX|')') && (s=execute(c, af, an))==TRUE);
  591.                 kbdmop = NULL;
  592.         } while (s==TRUE && --n);
  593.         return (s);
  594. }
  595.  
  596. /*
  597.  * Abort.
  598.  * Beep the beeper.
  599.  * Kill off any keyboard macro,
  600.  * etc., that is in progress.
  601.  * Sometimes called as a routine,
  602.  * to do general aborting of
  603.  * stuff.
  604.  */
  605. ctrlg(f, n)
  606. int f, n;
  607. {
  608.         (*term.t_beep)();
  609.         if (kbdmip != NULL) {
  610.                 kbdm[0] = (CTLX|')');
  611.                 kbdmip  = NULL;
  612.         }
  613.         mlwrite("Aborting");
  614.         return (ABORT);
  615. }
  616.  
  617. /* CLEARFLAG clears buffer change flag.  Bound to M-~
  618.  */
  619. clearflag(f, n)
  620. register int f, n;
  621. {
  622.         curwp->w_bufp->b_flag &= ~BFCHG;
  623.         upmode();
  624.         return(TRUE);
  625. }
  626.  
  627. /* UNKNCOM  Returns abort and beeps keyboard.  Bound to unused keys.
  628.  */
  629. unkncom(f, n)
  630. register int f, n;
  631. {
  632.         mlwrite("Unbound command!");
  633.         (*term.t_beep)();
  634.         return(ABORT);
  635. }
  636.  
  637. /* RETVERSION  Returns version string.  Bound to CTLX *.
  638.  */
  639. retversion(f, n)
  640. register int f, n;
  641. {
  642.         return(mlwrite("Writer: D. G. Conroy Revised: R. D. Royar (%s)",
  643.         version));
  644. }
  645.